/*
 * Copyright (c) 2009 Frederic Daoud
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
package org.stripesbook.rayures.repository.impl;

import org.stripesbook.rayures.repository.Dao;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import org.hibernate.Session;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

@Repository("dao")
@Transactional
public class DaoImpl implements Dao {
  @PersistenceContext
  private EntityManager entityManager;

  public <T> List<T> loadAll(Class<T> modelClass, String... orderByFields) {
    return getList(modelClass, createQuery(modelClass, Collections.<String, Object>emptyMap(), orderByFields));
  }

  public <T> T findById(Class<T> modelClass, Object id) {
    return entityManager.find(modelClass, id);
  }

  public <T> T findSingleByField(Class<T> modelClass, String field, Object value) {
    return getSingle(modelClass, createQuery(modelClass, field, value));
  }

  public <T> T findSingleByFields(Class<T> modelClass, Map<String, Object> fieldValueMap) {
    return getSingle(modelClass, createQuery(modelClass, fieldValueMap));
  }

  public <T> T findSingleByQuery(Class<T> modelClass, String queryString, Object... parameters) {
    return getSingle(modelClass, createQuery(queryString, parameters));
  }

  public <T> List<T> findListByField(Class<T> modelClass, String field, Object value, String... orderByFields) {
    return getList(modelClass, createQuery(modelClass, field, value, orderByFields));
  }

  public <T> List<T> findListByFields(Class<T> modelClass, Map<String, Object> fieldValueMap, String... orderByFields) {
    return getList(modelClass, createQuery(modelClass, fieldValueMap, orderByFields));
  }

  public <T> List<T> findListByQuery(Class<T> modelClass, String queryString, Object... parameters) {
    return getList(modelClass, createQuery(queryString, parameters));
  }

  public Object save(Object modelObject) {
    entityManager.persist(modelObject);
    return ((Session) entityManager.getDelegate()).getIdentifier(modelObject);
  }

  public void remove(Object modelObject) {
    entityManager.remove(modelObject);
  }

  public void removeById(Class<?> modelClass, Object idValue) {
    entityManager.remove(findById(modelClass, idValue));
  }

  protected <T> T getSingle(Class<T> modelClass, Query query) {
    try {
      return (T) query.getSingleResult();
    }
    catch (NoResultException exc) {
      return null;
    }
  }

  protected <T> List<T> getList(Class<T> modelClass, Query query) {
    try {
      return query.getResultList();
    }
    catch (NoResultException exc) {
      return Collections.emptyList();
    }
  }

  protected Query createQuery(String queryString, Object... parameters) {
    Query query = entityManager.createQuery(queryString);
    for (int i = 0, t = parameters.length; i < t; i++) {
      query.setParameter(i + 1, parameters[i]);
    }
    return query;
  }

  protected <T> Query createQuery(Class<T> modelClass, String field, Object value, String... orderByFields) {
    StringBuilder queryString = new StringBuilder("select m from " + modelClass.getName() + " m where m." + field + " = ?");
    appendOrderBy(queryString, orderByFields);
    Query query = entityManager.createQuery(queryString.toString());
    query.setParameter(1, value);
    return query;
  }

  protected <T> Query createQuery(Class<T> modelClass, Map<String, Object> fieldValueMap, String... orderByFields) {
    StringBuilder queryString = new StringBuilder("select m from " + modelClass.getName() + " m");
   
    if (!fieldValueMap.isEmpty()) {
      queryString.append(" where ");
    }

    for (Entry<String, Object> fieldValue : fieldValueMap.entrySet()) {
      String field = fieldValue.getKey();
      queryString.append("m." + field + " = :" + field);
      queryString.append(" and ");
    }
    // Remove the last 'and'
    if (!fieldValueMap.isEmpty()) {
      queryString.setLength(queryString.length() - " and ".length());
    }
    appendOrderBy(queryString, orderByFields);

    Query query = entityManager.createQuery(queryString.toString());
    for (Entry<String, Object> fieldValue : fieldValueMap.entrySet()) {
      String field = fieldValue.getKey();
      Object value = fieldValue.getValue();
      query.setParameter(field, value);
    }
    return query;
  }

  protected void appendOrderBy(StringBuilder queryString, String... orderByFields) {
    if (orderByFields.length > 0) {
      queryString.append(" order by ");
      for (String field : orderByFields) {
        queryString.append(field).append(',');
      }
      // Remove last ','
      queryString.deleteCharAt(queryString.length() - 1);
    }
  }
}
